package com.wjk.kylin.core.common.aspect;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.wjk.kylin.core.constant.CommonConstant;
import com.wjk.kylin.core.utils.HttpContextUtils;
import com.wjk.kylin.core.utils.IpUtils;
import com.wjk.kylin.core.utils.SpringContextHolder;
import com.wjk.kylin.modules.system.log.entity.OperateLog;
import com.wjk.kylin.modules.system.log.service.ILogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 操作日志记录处理
 *
 * @author ruoyi
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    // 配置织入点
    @Pointcut("@annotation(com.wjk.kylin.core.common.aspect.Log)")
    public void logPointCut() {}

    /**
     * 处理完请求后执行  返回通知
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作 异常通知
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    /**
     * 保存日志操作
     * @param joinPoint
     * @param e
     * @param jsonResult
     */
    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
        try {
            // 获得注解
            Log aspectLog = getAnnotationLog(joinPoint);
            if (aspectLog == null) {
                return;
            }

            // *========数据库日志=========*//
            OperateLog operateLog = new OperateLog();
            operateLog.setStatus(CommonConstant.BusinessStatus.SUCCESS.getCode());
            // 请求的地址
            String ip = IpUtils.getIpAddr(HttpContextUtils.getHttpServletRequest());
            operateLog.setIp(ip);
            // 返回参数 list too lang
            if(aspectLog.isSaveResponseData()){
                operateLog.setResponseData(JSONObject.toJSONString(jsonResult));
            }

            operateLog.setUrl(HttpContextUtils.getHttpServletRequest().getRequestURI());

            if (e != null) {
                operateLog.setStatus(CommonConstant.BusinessStatus.FAIL.getCode());
                operateLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 5000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operateLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operateLog.setRequestMethod(HttpContextUtils.getHttpServletRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, aspectLog, operateLog);
            // 写入数据库当中
            SpringContextHolder.getBean(ILogService.class).save(operateLog);
        } catch (Exception ex) {
            // 记录本地异常日志
            log.error("aop通知记录日志异常,异常信息:", ex);
        }
    }


    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param aspectLog 注解日志
     * @param operateLog       操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log aspectLog, OperateLog operateLog) throws Exception {
        // 设置action动作
        operateLog.setBusinessType(aspectLog.businessType().getCode());
        // 设置标题
        operateLog.setTitle(aspectLog.title());
        // 是否需要保存request，参数和值
        if (aspectLog.isSaveRequestData()) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, operateLog);
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param operateLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, OperateLog operateLog) throws Exception {
        String requestMethod = operateLog.getRequestMethod();
        if (CommonConstant.HttpMethod.PUT.name().equals(requestMethod) || CommonConstant.HttpMethod.POST.name().equals(requestMethod)) {
            operateLog.setRequestData(argsArrayToString(joinPoint.getArgs()));

        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) HttpContextUtils.getHttpServletRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            if(CollectionUtil.isNotEmpty(paramsMap)){
                operateLog.setRequestData(JSONObject.toJSONString(paramsMap));
            }else{
                paramsMap = getParameterNames(HttpContextUtils.getHttpServletRequest());
                operateLog.setRequestData(JSONObject.toJSONString(paramsMap));
            }

        }
    }

    /**
     * 从request 获取参数组装成map
     * @param request
     * @return
     */
    public static Map<String, String> getParameterNames(HttpServletRequest request) {
        Map<String, String> parameters = new HashMap<>();
        Enumeration<String> parameterNames = request.getParameterNames();

        while(parameterNames.hasMoreElements()) {
            String parameterName = parameterNames.nextElement();
            parameters.put(parameterName, request.getParameter(parameterName));
        }

        return parameters;
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuffer sb = new StringBuffer();
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {
                if (!isFilterObject(paramsArray[i])) {
                    sb.append(JSONObject.toJSONString(paramsArray[i])).append(" ");
                }
            }
        }
        return sb.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param obj 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    public boolean isFilterObject(final Object obj) {
        return obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse;
    }
}
